package nn.networks;

import java.util.ArrayList;

import nn.Neuron;
import nn.layers.Layer;
import nn.phraseparameters.CPPhraseParameters;

public class CPNetwork extends KohonenNetwork {
	private float grosLearnSpeed;
	private boolean printKohonenResults = false;
	private boolean smallEpochNormalize = true;
	private boolean normalizeCalcInput = true;
	private boolean tokenizeKohOutput = true;
	boolean printEpochs = true;
	int maxEpoch = 100;
	public int[] list = new int[9];

	public float[] normalize(float[] input) {
		float sum = 0;
		int inputSize = input.length;
		float[] newInput = new float[inputSize];
		for (int i = 0; i < inputSize; ++i) {
			sum += input[i];
		}
		if (sum == 0) {
			sum = 1;
		}
		for (int i = 0; i < inputSize; ++i) {
			newInput[i] = input[i] / sum;
		}
		return newInput;
	}

	@Override
	protected void checkLayerCount() {
		if (layerList.size() != 3) {
			throw new RuntimeException("number of layers is not equal 3, it is:" + layerList.size());
		}
	}

	public void epochLearn(ArrayList<CPPhraseParameters> kohPhraseParList, ArrayList<float[]> inputsList,
			ArrayList<float[]> outputsList) throws Exception {

		// TODO - implement

		CPNetwork net = this;
		int phrase = -1;
		for (int epoch = 0; epoch < kohPhraseParList.get(kohPhraseParList.size() - 1).epochThred; ++epoch) {
			if (epoch % 1000 == 0 && printEpochs) {
				System.out.println(epoch + ", ");
			}
			if (epoch < kohPhraseParList.get(0).epochThred) {
				if (phrase != 0) {
					System.out.println("Phrase: 0");
					net.layerList.get(1).setNeighborhoodSize(kohPhraseParList.get(0).neighCount);
					phrase = 0;
				}
				net.kohLearnSpeed = kohPhraseParList.get(0).kohLearnSpeed;
				net.grosLearnSpeed = kohPhraseParList.get(0).grosLearnSpeed;
			} else if (epoch < kohPhraseParList.get(1).epochThred) {
				if (phrase != 1) {
					System.out.println("Phrase: 1");
					net.layerList.get(1).setNeighborhoodSize(kohPhraseParList.get(1).neighCount);
					phrase = 1;
				}
				net.kohLearnSpeed = kohPhraseParList.get(1).kohLearnSpeed;
				net.grosLearnSpeed = kohPhraseParList.get(1).grosLearnSpeed;
			} else if (epoch < kohPhraseParList.get(2).epochThred) {
				if (phrase != 2) {
					System.out.println("Phrase: 2");
					net.layerList.get(1).setNeighborhoodSize(kohPhraseParList.get(2).neighCount);
					phrase = 2;
				}
				net.kohLearnSpeed = kohPhraseParList.get(2).kohLearnSpeed;
				net.grosLearnSpeed = kohPhraseParList.get(2).grosLearnSpeed;
			} else if (epoch < kohPhraseParList.get(3).epochThred) {
				if (phrase != 3) {
					System.out.println("Phrase: 3");
					net.layerList.get(1).setNeighborhoodSize(kohPhraseParList.get(3).neighCount);
					phrase = 3;
				}
				net.kohLearnSpeed = kohPhraseParList.get(3).kohLearnSpeed;
				net.grosLearnSpeed = kohPhraseParList.get(3).grosLearnSpeed;
			}
			net.learn(inputsList, outputsList, epoch);
		}
	}

	private int findMax(float[] result) {
		float max = result[0];
		int maxPos = 0;
		for (int resIt = 1; resIt < result.length; ++resIt) {
			if (result[resIt] >= max) {
				max = result[resIt];
				maxPos = resIt;
			}
		}
		return maxPos;
	}

	private void learn(ArrayList<float[]> inputs, ArrayList<float[]> outputs, int epoch) throws Exception {

		Layer layer = layerList.get(1);
		Layer grossLayer = layerList.get(2);
		int outputsIt = 0;
		for (float[] inputIt : inputs) {
			// kohonen layer
			float[] tmpInputIt = normalize(inputIt);

			if (smallEpochNormalize) {
				if (epoch < maxEpoch) {
					tmpInputIt = normalize2(tmpInputIt, epoch);
				}
			}

			float[] kohResult = layer.getResults(tmpInputIt);
			int maxPos = findMax(kohResult);
			list[maxPos]++;
			// int closest = getClosestNeuron(inputIt);
			// moveNeurons(layer.getNeuronList().get(closest), inputIt);
			moveKohWinnerNeuron(maxPos, tmpInputIt);
//			int size = kohResult.length;
			// float[] grosInput = new float[size];
			// for (int i = 0; i < size; ++i) {
			// grosInput[i] = 0;
			// }
			// grosInput[maxPos] = 1;
			// grossberg layer
			moveGrossLayer(maxPos, outputs.get(outputsIt));
			++outputsIt;
		}
	}

	private float[] normalize2(float[] input, int epoch) {
		int size = input.length;
		float[] newInput = new float[size];
		float n2 = n2(epoch);
		for (int i = 0; i < size; ++i) {
			// TODO sqrt?
			newInput[i] = (float) (n2 * input[i] + (1 - n2) * Math.sqrt((float) 1 / (float) size));
			// System.out.printf("%f - %f, e: %d%n", input[i], newInput[i],
			// epoch);
		}
		return newInput;
	}

	private float n2(int epoch) {
		return (float) epoch / (float) maxEpoch;
	}

	protected void moveKohWinnerNeuron(int maxPos, float[] input) {
		Layer layer = layerList.get(1);
		Neuron neuron = layer.getNeuronList().get(maxPos);
		float[] oldWeights = neuron.getWeights();
		int size = oldWeights.length;
		float[] newNeights = new float[size];
		for (int i = 0; i < size; ++i) {
			// + bo input - old
			newNeights[i] = oldWeights[i] + (kohLearnSpeed * (input[i] - oldWeights[i]));
		}
		neuron.setWeights(newNeights);
	}

	protected void moveGrossLayer(int maxPos, float[] outputs) {
		Layer grossLayer = layerList.get(2);
		int outputIt = 0;
		for (Neuron neuron : grossLayer.getNeuronList()) {
			float[] weights = neuron.getWeights();
			float prevalue = weights[maxPos];
			float yi = prevalue;
			weights[maxPos] = prevalue - (grosLearnSpeed * (yi - outputs[outputIt]));
			neuron.setWeights(weights);
			++outputIt;
		}
	}

	@Override
	public float[] calculate(float[] input) throws Exception {
		if (input.length != layerList.get(0).size) {
			throw new Exception("input size not match network input, " + input.length + " instead of "
					+ layerList.get(0).size);
		}
		float[] result;
		float[] kohInput;
		float[] kohResult;
		float[] grosInput;
		if (normalizeCalcInput) {
			kohInput = normalize(input);
		} else {
			kohInput = input;
		}

		kohResult = layerList.get(1).getResults(kohInput);
		int maxPos = findMax(kohResult);
		grosInput = new float[kohResult.length];
		for (int resIt = 0; resIt < grosInput.length; ++resIt) {
			grosInput[resIt] = 0;
		}
		grosInput[maxPos] = 1;
		result = layerList.get(2).getResults(grosInput);

		// for (int it = 1; it < layerList.size(); ++it) {
		// result = layerList.get(it).getResults(result);
		// }

		kohResult = layerList.get(1).getResults(normalize(input));
		if (printKohonenResults) {
			for (int i = 0; i < kohResult.length; ++i)
				System.out.print(kohResult[i] + " ");
			System.out.println();
		}
		return result;
	}
}
